package de.rj.urwalking.ui;

import android.content.Context;
import android.graphics.PointF;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.view.MotionEvent;

import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Matrix;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;

import de.ralf.urwalking.soplugin.EPSGPoint;
import de.ralf.urwalking.soplugin.SOQueryBuilder;
import de.rj.urwalking.config.Config;
import de.rj.urwalking.data.Pin;
import de.rj.urwalking.data.UserArrow;
import de.rj.urwalking.util.CameraUtil;

/**
 * Contains basic graphics stuff that is needed both indoors and outdoors.
 * 
 * @author rjj
 * 
 */
public abstract class BaseRenderer implements GLSurfaceView.Renderer {

	protected static final String TAG = Config.TAG;
	protected static final boolean DEBUG = Config.DEBUG;

	protected static final RGBColor PIN_COLOR = new RGBColor(128, 0, 0);
	protected static final int NONE = 0;
	protected static final int DRAG = 1;
	protected int mode = NONE;

	protected World world;
	protected Camera camera;
	protected FrameBuffer frameBuffer;
	protected UserArrow userArrow = null;

	protected SimpleVector[] cameraVectors;
//	protected int cameraMode;

	protected int perspective;
	
	private boolean movingLeft;
	private boolean movingRight;
	private boolean movingForward;
	private boolean movingBackward;

	private int fps;
	private long lastFrameTimestamp = System.currentTimeMillis();
	protected Context context;
	protected PointF startPoint = new PointF();
	protected float cameraFov;
	protected EPSGPoint[] triangleOffsets;
	protected float mOrientation;

	protected Callbacks mCallbacks;
	
	public BaseRenderer(Context context) {
		this.context = context;
	}

	protected void addUserArrow() {

		userArrow = new UserArrow(context);
		userArrow.translate(camera.getPosition().x, camera.getPosition().y, 0.5f);
		world.addObject(userArrow);

	}

	protected void updateCamera() {

		if (movingLeft) {
			world.checkCameraCollision(Camera.CAMERA_MOVELEFT, 0.2f, 1.5f, Camera.SLIDE);
		}
		if (movingRight) {
			world.checkCameraCollision(Camera.CAMERA_MOVERIGHT, 0.2f, 1.5f, Camera.SLIDE);
		}
		if (movingForward) {
			CameraUtil.moveForward(world);
		}
		if (movingBackward) {
			CameraUtil.moveBackward(world);
		}
	}

	public SimpleVector[] getCameraVectors() {
		if (camera == null) return null;
		return new SimpleVector[] { camera.getPosition(), camera.getDirection(), camera.getUpVector() };
	}

	public void setCameraVectors(SimpleVector[] cameraVectors) {

		this.cameraVectors = cameraVectors;
	}

	public void setCameraAngle(float angle) {

		if (camera != null) {
			CameraUtil.rotateHorizontalTo(angle, camera, perspective);
		}
	}

	public void rotateCamera(float angle) {
		CameraUtil.rotateHorizontal(angle, camera);
	}

	public SimpleVector getCameraPosition() {
		return camera.getPosition();
	}

	public Matrix getProjectionMatrix() {

		Matrix projectionMatrix = camera.getProjectionMatrix(frameBuffer);
		return projectionMatrix;
	}

	public void moveLeft() {
		movingLeft = true;
	}

	public void moveRight() {
		movingRight = true;
	}

	public void moveForward() {
		movingForward = true;
	}

	public void moveBackward() {
		movingBackward = true;
	}

	public void stopMovingLeft() {
		movingLeft = false;
	}

	public void stopMovingRight() {
		movingRight = false;
	}

	public void stopMovingForward() {
		movingForward = false;
	}

	public void stopMovingBackward() {
		movingBackward = false;
	}

	/**
	 * Logs how many frames per second are displayed.
	 */
	protected void logFps() {
		if (System.currentTimeMillis() - lastFrameTimestamp >= Config.FPS_LOG_INTERVAL) {
			Log.v(TAG, fps / (Config.FPS_LOG_INTERVAL / 1000f) + " fps");
			fps = 0;
			lastFrameTimestamp = System.currentTimeMillis();
		}
		fps++;
	}

	public void setFov(float fov) {
		camera.setFOV(fov);
	}

	public float getFov() {
		return camera.getFOV();
	}

	protected SimpleVector computeIntersectionWithFloor(SimpleVector ray) {
		// floor is defined by: z = 0
		// computing intersection with: x = camPos + λ * ray

		SimpleVector camPos = camera.getPosition();

		float lambda = -camPos.z / ray.z;

		ray.scalarMul(lambda);
		SimpleVector intersection = camPos.calcAdd(ray);
		return intersection;
	}

	/**
	 * Adds a new landmark which is displayed as a pin.
	 * 
	 * @param position
	 * @return an Object3D representing the new landmark
	 */
	public Pin addNewLandmark(SimpleVector position) {

		Pin newLandmark = new Pin(context);

		world.addObject(newLandmark);

		newLandmark.setAdditionalColor(PIN_COLOR);

		newLandmark.setOrigin(position);
		// fine-tune the position
		// TODO: may be fixed by setting a different origin when exporting the
		// obj in blender
		newLandmark.translate(new SimpleVector(0, 1, 1.1));

		if (DEBUG) Log.d(TAG, "added landmark at " + newLandmark.getOrigin());

		return newLandmark;
	}

	protected void lookAround(MotionEvent event) {

		float deltaX = event.getX() - startPoint.x;
		float deltaY = event.getY() - startPoint.y;

		float horizontalAngle = deltaX / 1000f * camera.getFOV();
		float verticalAngle = deltaY / 1000f * camera.getFOV();

		camera.rotateX(verticalAngle);
		CameraUtil.rotateHorizontal(horizontalAngle, camera);

		startPoint.set(event.getX(), event.getY());

	}

	public void zoomInEgoPerspective(float scaleFactor) {

		cameraFov = camera.getFOV() / scaleFactor;
		camera.setFOV(cameraFov);

	}

	public boolean adjustEgoPerspective(MotionEvent event) {

		if (camera == null) {
			return false;
		}

		switch (event.getAction() & MotionEvent.ACTION_MASK) {

		case MotionEvent.ACTION_DOWN:
		case MotionEvent.ACTION_POINTER_DOWN:

			startPoint.set(event.getX(), event.getY());
			mode = DRAG;
			break;

		case MotionEvent.ACTION_MOVE:
			if (mode == DRAG) { // movement of first finger
				lookAround(event);
			}
			return true;
		case MotionEvent.ACTION_UP:
			mode = NONE;
			break;
		case MotionEvent.ACTION_POINTER_UP:
			mode = NONE;
			break;
		}
		return false;

	}

	public void stepForward(float length) {

		if (world != null) {
			CameraUtil.stepForward(world, length);
		}
	}

	public void setCameraFov(float fov) {
		this.cameraFov = fov;
	}

	public float getCameraDirectionInRadians() {
		return (float) Math.atan2(camera.getDirection().y, camera.getDirection().x);
	}

	public void updateUserArrow(float direction) {
		if (world == null || camera == null) {
			return;
		}
		if (userArrow != null) {
			userArrow.rotateTo(direction + (float) Math.PI / 2);
			// TODO pivot stimmt nicht ganz!
		}
		// TODO: remove after rotation works properly
		triangleOffsets = SOQueryBuilder.calcPoints(new EPSGPoint(0, 0));
		mOrientation = (float) (direction + Math.PI/2);
		

	}
	
	
	public interface Callbacks {
		public void onWorldReady();
	}
	

}
